package com.his.emr.print;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import com.his.emr.print.entity.EleIdAndBounds;
import com.his.emr.print.utils.InlineElementDivision;
import com.his.emr.print.utils.My;
import com.his.emr.print.utils.MyDriver;
import com.his.emr.print.utils.MyFileWriter;
import com.his.emr.print.utils.StartAndEnd;

/**
 * date: 2018年2月15日<br>
 * 有点烦,明天再做吧. MD,为什么"segmentFault"上的那家伙用JS一瞬间就写出来了,我却要思考这么久呢?<br>
 * 也许可以考虑先用JS或Python写出来再转换成Java.我在构思java结构和类上浪费了不少时间!<br>
 * 
 * @author Wxg
 *
 */
public class HtmlDivision {
	public static final Pattern TAG_REGEX = Pattern.compile("(<.+?>)(</.+>)");
	public static final String WRAP_TAG = "emr"; //"span", emr, emrbreak;
	public static final String BREAK_SIGN = "<emrbreak></emrbreak>"; //"span", emr, emrbreak;
	
	private EleIdAndBounds ele;
	private String headHtml;
	
	private String filepath;
	
	private Document doc;
	private WebDriver driver;
	
	private List<WebElement> firstList;
	private List<WebElement> secondList;
	
	private String wholeHtml;
	
	private StringBuilder sbb4translate = null;
	
	private Stack<String> stack = null;
	
	public HtmlDivision(EleIdAndBounds ele, String headHtml, String wholeHtml){
		this.ele = ele;
		this.headHtml = headHtml;
		this.wholeHtml = wholeHtml;
		
		// 获取WebDriver
		driver = MyDriver.getStaticDriver();
		// 此片段会被拆分为两份(也许只有一份)
		firstList = new ArrayList<WebElement>();
		secondList = new ArrayList<WebElement>();
		// Add 2018年2月16日, 空指针
		stack = new Stack<String>();
	}
	
	public String done(){
		String tagName = ele.getTagName();
		if("table".equalsIgnoreCase(tagName)){
			tableDone();
		}else{
			normalDone();
		}
		return wholeHtml;
	}
	
	/**
	 * 普通标签需要递归
	 * &lt;emr&gt;&lt;/emr&gt; &lt;emr-break&gt;&lt;/emr-break&gt;
	 */
	private void normalDone() {
		// 首先,将html中的元素都添加上一个&lt;emr&gt;标签
		doc = Jsoup.parse(ele.getOuterHtml());
		Element body = doc.body();
		Element firstChild = body.children().first();
		sbb4translate = new StringBuilder();
		loopChildNode( firstChild );
		// 重新创建一个`Document`,包含上面的处理结果,并添加上"headHtml",并且保存到磁盘
		Document doc2 = Jsoup.parse( sbb4translate.toString() );
		doc2.head().html( headHtml );
		MyFileWriter mfw = new MyFileWriter();
		mfw.writeb( doc2.toString() );
		mfw.close();
		filepath = mfw.getFilepath();
		// WebDriver出场
		driver.get( My.fileUrl(filepath) );
		List<WebElement> eleList = driver.findElements(By.tagName( WRAP_TAG ));
		int breakIndex = 0;
		if(null != eleList){
			int currentY = 0;
			int H = WhosOverPageV2.MAIN_HEIGHT;
			int h = ele.getStartHeightInPage();
			for(int i=0; i<eleList.size(); i++){
				WebElement ele = eleList.get(i);
				if( ele.getLocation().y > currentY ){
					currentY = ele.getLocation().y;
					h += ele.getSize().height;
					if( h > H){
						// 这里和table的处理不同,如果发现高度已经超出,记下index,就退出循环
						breakIndex = i; //i之前的元素都分到上一页,i之后的都分到下一页
						break;
					}
				}
			}
		}
		//上面的代码只是得到了"breakIndex",下面要使用Jsoup来将内容分开
		Elements eles = doc2.getElementsByTag( WRAP_TAG );
		if(null != eles){
			// 这个for循环做的事情是: 1)插入一个break的标记, 2)将`WRAP_TAG`移除
			for(int i=0;i<eles.size();i++){
				Element ele = eles.get(i);
				if(i==breakIndex){
					ele.before( BREAK_SIGN );
				}
				ele.before(ele.text());
				ele.remove();
			}
		}
		// 以上代码是插入了break标记,并且清除了`WRAP_TAG`. 现在要从break标记开始将html内容分为两部分
		String htmlFragment = doc2.body().children().first().toString();
		String breakSign = "span";
		Matcher matcher = Pattern.compile("<(.+?)></.+?>").matcher(BREAK_SIGN);
		if(matcher.find()){breakSign = matcher.group(1);}
		InlineElementDivision ied = new InlineElementDivision(htmlFragment, breakSign);
		// 设置id属性
		Document doc3 = Jsoup.parse( ied.done() );
		Elements eles3 = doc3.getElementsByTag( ele.getTagName() );
		int id = 1;
		for(Element ele3 : eles3){
			ele3.attr("id", ele.id + "_" + (id++));
		}
		// 更新"wholeHtml"
		Document doc4 = Jsoup.parse( wholeHtml );
		Element oldEle = doc4.getElementById(ele.id);
		oldEle.before( doc3.body().children().first().toString() );
		oldEle.remove();
		wholeHtml = doc4.toString();
		// 至于将内容写入磁盘,就交给其他程序来处理了!
	}
	
	/**
	 * 表格,直接按照tr来拆分！
	 */
	private void tableDone() {
		/**
		 * 准备好将一个html片段转换为一个包含完整head的html内容
		 * head来自原html
		 */
		doc = Jsoup.parse(ele.getOuterHtml());
		Element head = doc.head();
		head.html(headHtml);
		
		// 用来获取标签的头和尾
		StartAndEnd sae = new StartAndEnd(ele.getOuterHtml());
		
		// 对于table没有额外的处理,直接输出到磁盘
		MyFileWriter mfw = new MyFileWriter();
		mfw.writeb(doc.toString());
		mfw.close(); // 别忘记close,像这种只写入一次的情况,应该提供一个自动关闭的API,以后再添加吧!
		filepath = mfw.getFilepath();
		// 将地址转换为url字符串,以便于WebDriver调用
		driver.get( My.fileUrl(filepath) );
		// 以下是处理过程,仍然是高度的累加和比较
		List<WebElement> trList = driver.findElements(By.tagName("tr"));
		int H = WhosOverPageV2.MAIN_HEIGHT;
		int h = ele.getStartHeightInPage();
		for(WebElement tr : trList){
			h += tr.getSize().height;
			if( h > H){
				secondList.add(tr);
			}else{
				firstList.add(tr);
			}
		}
		// 添加上头和尾
		StringBuilder sbb = new StringBuilder();
		if(firstList.size() > 0){
			sbb.append(sae.getStartTagStr());
			for(WebElement we : firstList){
				sbb.append(we.getAttribute("outerHTML"));
			}
			sbb.append(sae.getEndTagStr());
		}
		if(secondList.size() > 0){
			sbb.append(sae.getStartTagStr());
			for(WebElement we : secondList){
				sbb.append(we.getAttribute("outerHTML"));
			}
			sbb.append(sae.getEndTagStr());
		}
		// 重新设置id属性
		Document doc2 = Jsoup.parse(sbb.toString());
		Elements tables = doc2.getElementsByTag("table");
		int _id = 1;
		for(Element table : tables){
			table.attr("id", ele.id + "_" +(_id++));
		}
		// 更新"wholeHtml"
		Document doc3 = Jsoup.parse( wholeHtml );
		Element oldEle = doc3.getElementById(ele.id);
		oldEle.before( doc2.body().children().first().toString() );
		oldEle.remove();
		wholeHtml = doc3.toString();
		// 至于将内容写入磁盘,就交给其他程序来处理了!
	}

	public String getWholeHtml() {
		return wholeHtml;
	}
	
	/**
	 * 递归查找子元素
	 * @param node
	 */
	private void loopChildNode(Node node){
		String tagStart = "";
		String tagEnd = "";
		if(node.childNodeSize() == 0){ // 如果没有子元素(Node)说明是叶子节点
			sbb4translate.append( addTagEveryWord( node.outerHtml(), WRAP_TAG ) );
			if( null == node.nextSibling() ){
				/**
				 * 1) 我第二次看这段`if`代码时没明白当时为什么要这么写,再次测试以后才明白.所以这段注释非常重要!
				 * 2) <p style='width:80%;'>现病史:右上腹部疼痛，<span style='color:red;'>呈<b>持续性</b>闷痛，</span>逐渐加重，</p>
				 * 3) 以上面一段代码来说明: 
				 * -	* "持续性"三个字被`b`标签包裹, 它后面没有其他元素了,即没有`nextSibling`了,那么它的结束标签将会弹出
				 * -	* "呈"字后面就有其他节点,上面的被b标签包含的"持续性"就是它的`nextSibling`,所以就不能弹出结束标签. 一直到"闷痛，"才会弹出结束标签
				 * 4) 举了上面的例子,应该再次阅读此段代码就不会迷惑了!
				 */
				sbb4translate.append( stack.pop() );
			}
		}else{ // 有子元素的情况
			// 1. 首先计算出元素的起始标签和结束标签(起始标签往往包含内联样式)
			String tagStr = ((Element)node.clone()).empty().toString();
			String[] arr = getStartAndEnd(tagStr);
			tagStart = arr[0];
			tagEnd = arr[1];
			// 2. 先将起始标签添加到"StringBuilder"中
			sbb4translate.append(tagStart);
			// 3. 将结束标签添加到栈中(先碰到的标签后取出)
			stack.push(tagEnd);
			
			// 4. 递归调用
			List<Node> nodes = node.childNodes();
			for(Node n : nodes){
				loopChildNode(n);
			}
		}
	}
	
	/**
	 * 
	 * @param contents
	 * @param tagName
	 * @return
	 */
	private String addTagEveryWord(String contents, String tagName){
		if(null == contents || null == tagName){
			return "";
		}
		StringBuilder sbb = new StringBuilder();
		// 将字符串拆成字符数组(不知是否能处理上标?)
		char[] words = contents.toCharArray();
		if(null != words){
			for(char ch : words){
				sbb.append("<"+tagName+">");
				sbb.append(ch);
				sbb.append("</"+tagName+">");
			}
		}
		return sbb.toString();
	}
	
	/**
	 * 获得标签的Start 和 End
	 * {@link com.his.emr.print.utils.StartAndEnd}
	 * 
	 * @param htmlStr
	 * @return
	 */
	private String[] getStartAndEnd(String htmlStr){
		String[] arrStartAndEnd = new String[2];
		Matcher matcher = TAG_REGEX.matcher(htmlStr);
		if(matcher.matches()){
			arrStartAndEnd[0] = matcher.group(1);
			arrStartAndEnd[1] = matcher.group(2);
		}
		return arrStartAndEnd;
	}
	
}
